﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Net.Sockets;
using System.Threading;
using System.Text;

namespace Dummy.Web
{
    public class GameClient
    {
        TcpClient _client = null;
        NetworkStream _clientStream = null;
        ManualResetEvent _clientWriteFlag = new ManualResetEvent(true);
        Thread _clientThread = null;
        byte[] _buffer = new byte[1024];
        string dataBuffer = null;
        object _gameService;
        Encoding _encoding = Encoding.UTF8;

        public GameClient(TcpClient client, object service)
        {
            _client = client;
            _clientStream = client.GetStream();
            _gameService = service;
            StartClient();
        }

        public void StartClient()
        {
            _clientThread = new Thread(processCommand);
            _clientThread.Start();

            //debug
            /*
            sendHello();
            Timer timer = new Timer(
                (object obj)=>{
                    sendServiceClientCommand("hi", "my name is ...");
                }, null, 5000, 10000);
            */
        }

        public void StopClient()
        {
            _client.Close();
        }

        private void sendHello()
        {
            sendData("hello, client!\r\n");
        }

        private void sendData(string data)
        {
            _clientWriteFlag.WaitOne();
            _clientWriteFlag.Reset();
            try
            {
                byte[] bytes = _encoding.GetBytes(data);
                _clientStream.Write(bytes, 0, bytes.Length);
            }
            catch (Exception ex)
            {
            }
            _clientWriteFlag.Set();
        }

        private void processCommand()
        {
            while (_client.Connected)
            {
                try
                {
                    int len = _clientStream.Read(_buffer, 0, _buffer.Length);
                    string newdata = _encoding.GetString(_buffer, 0, len);
                    processCommand(newdata);
                }
                catch (Exception ex)
                {

                }
            }
        }

        private void processCommand(string newdata)
        {
            dataBuffer += newdata;
            if (newdata.Contains("\r\n"))
            {
                if (dataBuffer.StartsWith("close"))
                    this.StopClient();
                else
                {
                    if (!invokeServiceCommand(dataBuffer))
                        //sendData("unknown command\r\n");
                        sendServiceClientCommand(GameClientCommand.error, "unknown command");
                    dataBuffer = "";
                }
            }
        }

        public void sendServiceClientCommand(GameClientCommand name, params string[] parameters)
        {
            string command = GameClient.GetCallbackCommand(name, parameters);
            sendData(command);
        }

        public static string GetCallbackCommand(GameClientCommand name, params string[] parameters)
        {
            return Enum.GetName(typeof(GameClientCommand), name) + ";" + string.Join(",", parameters) + "\r\n";
        }

        public static string GetCallbackParameter(string[] parameter)
        {
            return string.Join(":", parameter);
        }

        private bool invokeServiceCommand(string command)
        {
            try
            {
                if (_gameService == null)
                    throw new Exception();
                
                command = command.Replace("\r\n", "").Replace("\b", "").Trim();
                string[] cm = command.Split(';');
                if (cm.Length == 0)
                    throw new Exception();

                MethodInfo mi = _gameService.GetType().GetMethod(cm[0]);
                if ((mi == null) || (!mi.IsPublic))
                    throw new Exception();
            
                string[] psplit = command.EndsWith(";") ? new string[0] : cm[1].Split(',');
                object[] parameters = getMethodParams(psplit, mi.GetParameters());
                if (parameters == null)
                    throw new Exception();

                object res = mi.Invoke(_gameService, parameters);
                return true;
            }
            catch (Exception ex)
            {
                invokeErrorCommand(command);
                return false;
            }
        }

        private void invokeErrorCommand(string wrongcommand)
        {
            _gameService.GetType().GetMethod("errorcommand").Invoke(_gameService, new object[] { wrongcommand });
        }

        private object[] getMethodParams(string[] prms, ParameterInfo[] ps)
        {
            if (prms.Length != ps.Length - 1)
                return null;
            List<object> prmsO = new List<object>();
            prmsO.Add(new GameClientCallback(this.sendServiceClientCommand)); //callback is always first parameter
            for (int i = 1; i < ps.Length; i++)
            {
                if (ps[i].ParameterType == typeof(string))
                {
                    prmsO.Add(prms[i - 1]);
                }
                else if (ps[i].ParameterType == typeof(int))
                {
                    int p;
                    if (!int.TryParse(prms[i - 1], out p))
                        return null;
                    prmsO.Add(p);
                }
                else if (ps[i].ParameterType == typeof(bool))
                {
                    if ((prms[i - 1].ToLower() != "true") && (prms[i - 1].ToLower() != "false"))
                        return null;
                    prmsO.Add(prms[i - 1].ToLower() == "true");
                }
                else if (ps[i].ParameterType == typeof(string[]))
                {
                    prmsO.Add(prms[i - 1].Split(':'));
                }
                else
                    return null;
            }
            return prmsO.ToArray();
        }
    }

    public delegate void GameClientCallback(GameClientCommand name, params string[] parameters);
}